#!/usr/bin/python


import sys

from constants import *
import dbus
from pyfuncs import *
from time import *
from os import *
from my_exceptions import *
from datetime import datetime

import logging
logger = logging.getLogger("Interface")
logger.setLevel(logging.DEBUG+1)




"""
~~~~~~~~~~~~~~~~~~~~ NOTES ON THIS CLASS ~~~~~~~~~~~~~~~~~~~

# 1. It is very important to close every initialized iterator (iterator.close()). Indeed, an open iterator seems to block the dbus communication to Soprano, causing the access to be "denied" to other methods, even if they apply on different objects like on a model, e.g. model.deleteStatement().

"""





#**********************************************************#

class Interface (object): # Inherits from object
	
	
	#~~~~~~~~~~~~~~~~~~~~~~~~~ BEGIN  ~~~~~~~~~~~~~~~~~~~~~~~~~#
	
	#~ This is the Python code for an advanced Singleton Design Pattern use. ~#
	#~ see http://en.wikipedia.org/wiki/Singleton_pattern#Python for more information (available on the 2008-07-07). ~#
	#~ If you want to inherit from Interface, you should study the subject and possibly rewrite it (see the same web page for details). ~#
	
	# static attributes of the class Interface
	instance = None
	model = None
	
	# standard construction method in Python
	def __new__(interfaceClass, results_path, service_name="org.kde.NepomukStorage", model_name="/org/soprano/Server/models/main"):
		# may raise a dbus.exceptions.DBusException if the org.kde.NepomukStorage is not accessible
		
		logger.debug("Interface.__new__")
		if interfaceClass.instance is None:
			
			# initializations (run only once)
			logger.debug("\t(first time)")
			self_obj = object.__new__(interfaceClass)
			interfaceClass.instance = self_obj
			
			self_obj.service_name = service_name
			
			self_obj.model = dbus.SessionBus().get_object(self_obj.service_name, model_name)
			
		return interfaceClass.instance
		
	# TODO: add an assertion? (see given web page)
	
	#~~~~~~~~~~~~~~~~~~~~~~~~~  END   ~~~~~~~~~~~~~~~~~~~~~~~~~#
	
	
	def __init__(self, results_path, service_name="org.kde.NepomukStorage", model_name="/org/soprano/Server/models/main"):
		
		logger.debug("Interface.__init__")
		
		self.results_path = results_path
		
		# make sure we will be able to write in this folder results_path
		if not access(results_path, F_OK):
			makedirs(results_path)
		
		self.derivationHashmap = {}
		self.log = file(results_path+"log.txt", 'w')
		
############################################################
	
	# returns an ID, unique for ... (now it is unique regarding the main model)
	def getUniqueId(self):
		
		while(True):
			
			new_id = PATH_NEPOMUK+random_string(20)
			# let's request the model if a resource with that name already exists
			#print new_id
			query = "SELECT * WHERE { <" + new_id + "> ?rel ?obj }"
			query_result_iterator = self.executeQuery(query)
			
			# if there is at least one answer to the request (iterator.next() succeeds on the first try), the resource already exists and we have to generate a new one
			if (not query_result_iterator.next()):
				query_result_iterator.close()
				#print "does not exist... yet"
				return new_id
			
			query_result_iterator.close()
		
############################################################
	
	# /!\ See note 1 on top of that document.
	def executeQuery(self, query, queryLang=QUERYLANG_SPARQL):
		
		self.log.write("query: "+query+"\n\n")
		#print "query = " + query + " ; queryLang = " + queryLang
		query_result_iterator_name = self.model.executeQuery(query, queryLang)
		query_result_iterator = dbus.SessionBus().get_object(self.service_name, query_result_iterator_name)
		return query_result_iterator
		
############################################################
	
	# Statement:
	# Node "resource" (subject) - the URI of the element: NODE_RESOURCE
	# Node "relation" (predicate) - #comment, #tag, ...: NODE_RESOURCE
	# Node "object" (other resource or literal value) - resource or value: NODE_RESOURCE | NODE_LITERAL
	# TODO Node "context" (storage graph) - resource or empty (emptyNode) for default graph ???
	
	def createStatement(self, subject_uri, predicate_type, object_value="", object_language=None):
		
		logger.debug(str(self.model) + ".createStatement")
		
		s = ""
		
		resource = None
		if subject_uri == "?":
			resource = EMPTY_NODE
		else:
			resource = dbus.Struct([NODE_RESOURCE, subject_uri, s, s])
			
		relation = None
		if predicate_type == "?":
			relation = EMPTY_NODE
		else:
			relation = dbus.Struct([NODE_RESOURCE, predicate_type, s, s])
		
		object = None
		if object_value == "?":
			object = EMPTY_NODE
		elif object_language == None:
			# the object node is a resource
			object = dbus.Struct([NODE_RESOURCE, object_value, s, s])
		else:
			# the object node is a literal value
			object = dbus.Struct([NODE_LITERAL, object_value, s, object_language])
			
		# TODO: an empty node for the context ?
		#context = EMPTY_NODE
		context = dbus.Struct([NODE_RESOURCE, DEFAULT_ONTOLOGY_URI, s, s])
		
		#print statement
		return dbus.Struct([resource, relation, object, context])
		
############################################################

	def registerStatement(self, subject_uri, predicate_type, object_value="", object_language=None):
		
		logger.debug(str(self.model) + ".registerStatement")
		
		statement = self.createStatement(subject_uri, predicate_type, object_value, object_language)
		
		logger.debug(str(statement))
		self.log.write("registerStatement("+subject_uri+", "+predicate_type+", "+object_value+", "+str(object_language)+")\n\n")
		self.log.write("model.addStatement: "+str(statement)+"\n\n")
		
		error_code = self.model.addStatement(statement)
		
		return error_code
		
############################################################

	def deleteStatement(self, subject_uri, predicate_type, object_value="", object_language=None):
		
		logger.debug(str(self.model) + ".deleteStatement")
		
		statement = self.createStatement(subject_uri, predicate_type, object_value, object_language)
		self.log.write("deleteStatement("+subject_uri+", "+predicate_type+", "+object_value+", "+str(object_language)+")\n\n")
		self.log.write("model.removeStatement: "+str(statement)+"\n\n")
		error_code = self.model.removeStatement(statement)
		
		return error_code
	
############################################################

	def deleteAllStatements(self, subject_uri, predicate_type, object_value="", object_language=None):
		
		logger.debug(str(self.model) + ".deleteAllStatements")
		
		statement = self.createStatement(subject_uri, predicate_type, object_value, object_language)
		self.log.write("deleteAllStatements: "+str(statement)+"\n\n")
		error_code = self.model.removeAllStatements(statement)
		
		return error_code

############################################################

	# Returns the image associated to the given resource by the given property in the database.
	# Returns None if the given resource has no image by the given property in the database.
	# Raises a DatabaseIncoherenceException if the given resource has more than one image by the given property.
	def getImage(self, resource_uri, property_uri):
		
		query = "SELECT * WHERE { <" + resource_uri + "> <" + property_uri + "> ?elt }"
		elts = self.executeQuery(query)
		
		my_elt = None
		if elts.next(): # resource_uri has an associated element
			my_elt = elts.current()[0]["elt"][1]
		
		if elts.next():
			my_second_elt = elts.current()[0]["elt"][1]
			elts.close()
			
			raise DatabaseIncoherenceException("\"" + resource_uri + "\" has more than one associated element by the property \"" + property_uri + "\" (at least \"" + my_elt + "\" and \"" + my_second_elt + "\").", self.results_path+"/error.log")
		
		elts.close()
		
		return my_elt
		
############################################################

	# TODO: explain
	def setImage(self, resource_uri, property_uri, image, image_language=None):
		
		error_code = self.removeImage(resource_uri, property_uri)
		error_code = self.registerStatement(resource_uri, property_uri, image, image_language)
		
		return 0 # TODO: returns a combination of error_codes...

############################################################

	# TODO: explain
	def removeImage(self, resource_uri, property_uri):
		
		error_code = self.deleteAllStatements(resource_uri, property_uri, "?")
		return error_code
		
############################################################

	# Returns the inverse property URI for the given property in the database.
	# Returns None if the given resource has no defined type in the database.
	# Raises a DatabaseIncoherenceException if there is more than one RDF type for the given resource.
	def getPimosInversePropertyOf(self, resource_uri):
		
		########## STORED IN PIMO ##########
		
		# get the inverse property when it is register in a particular sense
		query = "SELECT * WHERE { <" + resource_uri + "> <" + PROTEGE_INVERSEPROPERTY + "> ?iprop }"
		#print query
		iprops = self.executeQuery(query)
		
		iprop = None
		if iprops.next(): # resource_uri has a defined inverse property
			iprop = iprops.current()[0]["iprop"][1]
		
		if iprops.next():
			# there are more than one inverse property for the given property!
			second_iprop = iprops.current()[0]["iprop"][1]
			iprops.close()
			
			raise DatabaseIncoherenceException("\"" + resource_uri + "\" has more than one defined inverse property (at least \"" + iprop + "\" and \"" + second_iprop + "\").", self.results_path+"/error.log")
		
		iprops.close()
		
		# or in the other sense
		query = "SELECT * WHERE { ?iprop <" + PROTEGE_INVERSEPROPERTY + "> <" + resource_uri + "> }"
		#print query
		iprops2 = self.executeQuery(query)
		
		iprop2 = None
		if iprops2.next(): # resource_uri has a defined inverse property
			iprop2 = iprops2.current()[0]["iprop"][1]
		
		if iprops2.next():
			# there are more than one inverse property for the given property!
			second_iprop2 = iprops2.current()[0]["iprop"][1]
			iprops2.close()
			
			raise DatabaseIncoherenceException("\"" + resource_uri + "\" has more than one defined inverse property (at least \"" + iprop2 + "\" and \"" + second_iprop2 + "\").", self.results_path+"/error.log")
		
		iprops2.close()
		
		# test if the two (possibly) retrieved inverse_properties are equal.
		if (iprop != None) and (iprop2 != None) and (iprop != iprop2):
			raise DatabaseIncoherenceException("\"" + resource_uri + "\" has more than one defined inverse property (at least \"" + iprop + "\" and \"" + iprop2 + "\").", self.results_path+"/error.log")
		
		if iprop != None:
			return iprop
		else:
			return iprop2 # proper URI or None
		
############################################################

	# Returns True if the given resource is of the given type, or if it derives from that type, and False otherwise.
	def instanceOf (self, resource_uri, type_uri):
		
		resource_type = self.getImage(resource_uri, RDF_TYPE)
		logger.debug("interface.instanceOf(), "+resource_uri+" has rdf:type "+str(resource_type)+".")
		# TODO: is that correct if a resource has multiple types? if yes, allow it! (here getImage() can not get multiple images, i.e. multiple types).
		
		return self.derivesFrom(resource_type, type_uri)
		
############################################################

	# Returns True if the given child_type derives from the given parent_type, False otherwise.
	# Derivation at degree zero works, i.e. if the child and parent are equal, derivesFrom() returns True.
	# The derivation is an object-derivation here: the inheritance is detected via the rdfs:subClassOf property.
	def derivesFrom (self, child_type_uri, parent_type_uri):
		
		# TODO: what if there are cycles?
		
		if child_type_uri+" "+parent_type_uri in self.derivationHashmap:
			logger.debug("derivesFrom(hm) = "+str(self.derivationHashmap[child_type_uri+" "+parent_type_uri]))
			return self.derivationHashmap[child_type_uri+" "+parent_type_uri]
		
		logger.debug("child = "+child_type_uri)
		logger.debug("parent = "+parent_type_uri)
		if child_type_uri == parent_type_uri:
			logger.debug("derivesFrom(c=p) = True")
			return True
		
		t1 = datetime.today()
		query = "SELECT * WHERE { <" + child_type_uri + "> <" + RDFS_SUBCLASSOF + "> ?type }"
		parent_types = self.executeQuery(query)
		t2 = datetime.today()
		logger.debug("query time="+str(t2-t1))
		
		while parent_types.next():
			parent_type = parent_types.current()[0]["type"][1]
			self.derivationHashmap[child_type_uri+" "+parent_type] = True
			if self.derivesFrom(parent_type, parent_type_uri):
				parent_types.close()
				return True
		
		parent_types.close()
		
		self.derivationHashmap[child_type_uri+" "+parent_type_uri] = False
		logger.debug("derivesFrom(fail) = False")
		return False
		
############################################################

	# Clears the otherLists.rdf file.
	def clearOthersList(self):
		
		gen = rdf_gen(self.results_path+"/othersList.rdf")
		gen.write_list([], [])
		
		return 0

############################################################

	# Checks whether the given context is registered in the database (as an Ontology or InstanceBase)
	def getContextType(self, context_uri):
		
		context_type = self.getImage(context_uri, RDF_TYPE)
		
		if context_type == None:
			return None
		elif [NRL_INSTANCEBASE, NRL_ONTOLOGY].count(context_type) == 0:
			raise DatabaseIncoherenceException("The context \"" + context_uri + "\" has type \"" + context_type + "\", which is neither nrl:InstanceBase nor nrl:Ontology, the only two context types allowed.")
		else:
			return context_type

############################################################

	# Registers the context with the given context uri and type.
	def createContext(self, context_uri, context_type):
		
		# query the database to know if the graph/context where it will be stored already exists as a Ontology or InstanceBase.
		existing_context_type = self.getContextType(context_uri)
		
		if existing_context_type != None:
			raise CanNotException("The context \"" + existing_context_type + "\" already exists. Can not create it.")
		
		error_code = self.registerStatement(context_uri, RDF_TYPE, context_type)
		
		return error_code
	
############################################################

	def buildDownTree(self, parent_elts, downing_property):
		
		accepted_elts = parent_elts
		for parent_elt in parent_elts:
			# retrieve the children of this parent
			query = "SELECT ?child WHERE { ?child <" + downing_property + "> <" + parent_elt + "> }"
			childrenIt = self.executeQuery(query)
			children_elts = []
			while childrenIt.next():
				children_elts.append(childrenIt.current()[0]["child"][1])
			# add them to the list of accepted elts
			accepted_elts.extend(children_elts)
		
		return accepted_elts






